/*
 * Contexto.cpp
 *
 *  Created on: 07/06/2010
 *      Author: administrador
 */

#include "Contexto.h"
#include <string.h>
#include <stdio.h>
#include "PPMC.h"

Contexto::Contexto(string clave)
{
	this -> clave = clave;
	this -> totalFrecuencias = 0;
	this -> caracteres.clear();
	this->cantidadCaracteres = 0;
	Distribucion* Escape = new Distribucion(Distribucion::CARACTER_ESCAPE,0);
	this->caracteres.push_back(Escape);

}

Contexto::Contexto(){


}

Contexto::~Contexto()
{
	for (unsigned int i = 0; i < caracteres.size(); i++)
	{
		delete caracteres[i];
	}

}

string Contexto::getClave()
{
	return this -> clave;
}

typeFrecuencias Contexto::getTotalFrecuencias()
{
	return this -> totalFrecuencias;
}
int Contexto::getIndice(typeCaracterChar caracter)
{
	int mitad,izq,der;
	izq = 0;
	der = this->caracteres.size() - 1;

	typeCaracterChar caracterIzq = caracteres[izq] -> getCaracter();
	typeCaracterChar caracterDer = caracteres[der] -> getCaracter();
	while (izq <= der)
	{
		mitad = (izq + der) / 2;
		if (caracter > caracteres[mitad] -> getCaracter())
			izq = mitad + 1;
		else
			if (caracter < caracteres[mitad] -> getCaracter())
				der = mitad - 1;

			else
				return mitad;

	}

	return -1;
}

Distribucion* Contexto::getDistribucion(typeCaracterChar caracter)
{
	int indice = this -> getIndice(caracter);

	if (indice != -1){
	
		return caracteres[indice];
	}
	else
		return NULL;
}

void Contexto::agregarDistribucion(typeCaracterChar caracter)
{
	Distribucion* nueva = new Distribucion(caracter,1);
	//	this -> ordenarCaracteres();
	vector<Distribucion*>::iterator it;

	it = this->caracteres.begin();

	while ( (it!=this->caracteres.end()) && ( (typeCaracterAscii)(*it)->getCaracter() < caracter)){

		it++;

	}

	this->caracteres.insert(it,nueva);

	this -> totalFrecuencias += nueva -> getFrecuencia();

	this->cantidadCaracteres++;
}

//void Contexto::ordenarCaracteres()
//{
//	Distribucion* aux;
//	for (size_t i = 0; i < caracteres.size() - 1; i++)
//	{
//		for (size_t j = i + 1; j < caracteres.size(); j++)
//		{
//			if (caracteres[i]->getCaracter() > caracteres[j]->getCaracter())
//			{
//				aux = caracteres[i];
//				caracteres[i] = caracteres[j];
//				caracteres[j] = aux;
//			}
//		}
//	}
//}

typeFrecuencias Contexto::getFrecuencia(typeCaracterChar caracter)
{
	Distribucion* distribucion = this -> getDistribucion(caracter);

	if (distribucion != NULL)
		return distribucion -> getFrecuencia();
	else
		return 0;
}

typeFrecuencias Contexto::getFrecuenciaAcumulada(typeCaracterChar caracter)
{
	int frecuenciaAcumulada = 0;
	int indice = this -> getIndice(caracter);

	for (int i = 0; i < indice; i++)
	{
		frecuenciaAcumulada += caracteres[i] -> getFrecuencia();
	}

	return frecuenciaAcumulada;
}

void Contexto::actualizar(typeCaracterChar caracter)
{
	Distribucion* distribucion = this -> obtenerDistribucion(caracter);

	if (distribucion -> getCaracter() == Distribucion::CARACTER_ESCAPE)
	{
		this -> agregarDistribucion(caracter);
	}

	distribucion -> aumentarFrecuencia();
}

Distribucion* Contexto::obtenerDistribucion(typeCaracterChar caracter)
{
	Distribucion* buscada;

	buscada = this -> getDistribucion(caracter);

	if (buscada == NULL)
		buscada = this -> getDistribucion(Distribucion::CARACTER_ESCAPE);

	return buscada;
}

int Contexto::getOrden()
{
	if (this -> clave == PPMC::claveContextoOrdenCero)
		return 0;
	else
		return this -> clave.size();
}

void Contexto::acumularCaracter(typeCaracterChar caracter)
{

	Distribucion* distribucion = getDistribucion(caracter);
	if(distribucion == NULL)
	{
		this -> agregarDistribucion(caracter);
		if(caracter == Distribucion::CARACTER_ESCAPE)
		{
			distribucion -> aumentarFrecuencia();
		}
	}else
	{
		distribucion -> aumentarFrecuencia();
	}

}

bool Contexto::emitir(typeCaracterAscii caracter)
{
	Distribucion* distribucion = this -> obtenerDistribucion(caracter);
	
	// TODO aca deberia crearse un Emisor y relacionarlo con el aritmetico
//	Emisor* emisor = new Emisor(distribucion->getCaracter(), distribucion->getFrecuencia(), this -> getTotalFrecuencias(), this -> getFrecuenciaAcumulada(distribucion->getCaracter()));
//	delete emisor;
	
	// Tener en cuenta que si la distribucion es ESCAPE y tiene frecuencia 0, hay que emitirla con frecuencia 1
	
	if ((distribucion -> getCaracter() == Distribucion::CARACTER_ESCAPE) && (distribucion -> getFrecuencia() == 0))
	{
		Compresor::comprimir(distribucion->getCaracter(),1, 1, 0);
	}
	else
	{
		Compresor::comprimir(distribucion->getCaracter(), distribucion->getFrecuencia(), this -> getTotalFrecuencias(), this -> getFrecuenciaAcumulada(distribucion->getCaracter()));
	}
	
	this->totalFrecuencias++;

	distribucion->aumentarFrecuencia();


	if (distribucion -> getCaracter() == Distribucion::CARACTER_ESCAPE)
		return true;
	else
		return false;
}

typeCaracterAscii Contexto::emitirDescomprimido (){

	typeFrecuencias frecunciaTotal = this->getTotalFrecuencias()==0?1:this->getTotalFrecuencias();

	typeCaracterAscii caracterDescomprimido = Compresor::descomprimir(frecunciaTotal);

	vector<Distribucion*>::iterator iterator;

	iterator = this->caracteres.begin();

	int acumulado = 0;

	typeFrecuencias frecuenciaHastaCaracter = caracterDescomprimido;

	typeFrecuencias frecuenciaCaracter = (*iterator)->getFrecuencia()==0?1:(*iterator)->getFrecuencia();

	while (iterator!=this->caracteres.end() && frecuenciaHastaCaracter  >= (acumulado + frecuenciaCaracter)){
	
		acumulado += frecuenciaCaracter;

		iterator++;

		if (iterator!=this->caracteres.end()){

			frecuenciaCaracter = (*iterator)->getFrecuencia();
		}
	}

	Compresor::setFrecuenciasDescomprimir(frecuenciaCaracter, acumulado);

	cout << "el caracter descomprimido fue ... " << caracterDescomprimido << "la frecuencia total fue " << frecunciaTotal <<'\n';

	(*iterator)->aumentarFrecuencia();

	this->totalFrecuencias++;

	return (*iterator)->getCaracter();

}

Elemento* Contexto::serializar(){

	Clave claveElemento(this->clave);

	typeCaracterChar caracterDistribucion;

	typeFrecuencias frecuenciaDistribucion;

	int tamanioTotalValor = this->caracteres.size() * (sizeof(caracterDistribucion) + sizeof(frecuenciaDistribucion)) +sizeof(size_t)+sizeof(typeFrecuencias);

	typeCaracterChar* valor = new typeCaracterChar[tamanioTotalValor];

	size_t cantidadElementos = this->caracteres.size();

	typeFrecuencias frecuenciaTotal = this->totalFrecuencias;

	int position = sizeof(cantidadElementos) + sizeof(frecuenciaTotal);

	memmove((void*)valor,(void*)&cantidadElementos,sizeof(cantidadElementos));

	memmove((void*)(valor+sizeof(cantidadElementos)),(void*)&frecuenciaTotal,sizeof(frecuenciaTotal));

	vector<Distribucion*>::iterator iterator;

	iterator = this->caracteres.begin();

	while (iterator!=this->caracteres.end()){

		caracterDistribucion = (*iterator)->getCaracter();

		frecuenciaDistribucion = (*iterator)->getFrecuencia();

		memmove((void*)(valor+position),(void*)&caracterDistribucion,sizeof(typeCaracterChar));

		memmove((void*)(valor+position+sizeof(caracterDistribucion)),(void*) &frecuenciaDistribucion, sizeof(frecuenciaDistribucion));

		position += (sizeof(caracterDistribucion)+sizeof(frecuenciaDistribucion));

		iterator++;
	}

	std::string* valorElemento = new string((char*)valor,tamanioTotalValor);

	delete [] valor;

	return new Elemento(claveElemento,valorElemento);


}

void Contexto::hidratar(Elemento *elementoContexto){

	this->clave = elementoContexto->getClave().getClave();

	typeCaracterChar* valor = (typeCaracterChar*)(elementoContexto->getValor()->c_str());

	typeCaracterChar caracterDistribucion;

	typeFrecuencias frecuenciaDistribucion;

	this->caracteres.clear();

	size_t cantidadElementos = 0;

	typeFrecuencias frecuenciaTotal = 0;

	memmove((void*)&cantidadElementos,(void*)valor,sizeof(cantidadElementos));

	memmove((void*)&frecuenciaTotal,(void*)(valor+sizeof(cantidadElementos)),sizeof(frecuenciaTotal));

	this->totalFrecuencias = frecuenciaTotal;

	int posicion = sizeof(cantidadElementos)+ sizeof(frecuenciaTotal);

	for (size_t i=0; i<cantidadElementos; i++){

		memmove ((void*)&caracterDistribucion,(void*)(valor+posicion),sizeof(caracterDistribucion));

		memmove ((void*)&frecuenciaDistribucion,(void*)(valor+posicion+sizeof(caracterDistribucion)),sizeof(frecuenciaDistribucion));

 		Distribucion * distribucionHidratada = new Distribucion(caracterDistribucion,frecuenciaDistribucion);

		this->caracteres.push_back(distribucionHidratada);

		posicion += (sizeof(caracterDistribucion)+sizeof(frecuenciaDistribucion));
	}

}
